﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Utility.Helper
{
    public static class LinqExtension
    {

        /// <summary>
        /// 获取 PropertyInfo 集合
        /// </summary>
        /// <param name="type"></param>
        /// <param name="bindingFlags"></param>
        /// <returns></returns>
        public static PropertyInfo[] GetPropertyInfos(this Type type,
            BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public)
            => type.GetProperties(bindingFlags);

        /// <summary>
        /// 创建 对象实例
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static T CreateInstance<T>()
        {
            var type = typeof(T);
            if (type.IsValueType || typeof(T) == typeof(string))
                return default;
            return (T)Activator.CreateInstance(type);
        }

        /// <summary>
        /// 获取 对象 中 某个属性得 标记
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="type"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public static T GetAttribute<T>(this Type type, string name)
            where T : Attribute
            => GetPropertyInfo(type, name).GetCustomAttribute(typeof(T)) as T;

        /// <summary>
        /// 获取 PropertyInfo 对象
        /// </summary>
        /// <param name="type"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public static PropertyInfo GetPropertyInfo(this Type type, string name) => type.GetProperty(name);

        /// <summary>
        /// 获取 模型 有 Key 特性得 属性对象
        /// </summary>
        /// <param name="type"></param>
        /// <param name="isCheckKey"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public static PropertyInfo GetKeyProperty(this Type type, bool isCheckKey = true)
        {
            if (isCheckKey)
            {
                var keyAttribute = (KeyAttribute)Attribute.GetCustomAttributes(type, true).Where(item => item is KeyAttribute).FirstOrDefault();

                if (keyAttribute == null)
                {
                    throw new InvalidOperationException($"实体类型：{type.FullName}未设置主键标记 “[Key]” ");
                }
            }

            var propertyInfo = GetPropertyInfos(type)
                .FirstOrDefault(item => item.GetCustomAttribute(typeof(KeyAttribute)) != null);

            return propertyInfo;
        }

        /// <summary>
        /// 是否有 KeyAttribute 标记
        /// </summary>
        /// <param name="propertyInfo"></param>
        /// <returns></returns>
        public static bool HasKey(PropertyInfo propertyInfo)
            => propertyInfo.GetCustomAttribute(typeof(KeyAttribute)) != null;

        /// <summary>
        /// 生成表达式树 例如：( x=> new [] {1,2,3}.Contains(x.Key) )
        /// </summary>
        /// <param name="name"></param>
        /// <param name="values"></param>
        /// <param name="expName"></param>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="TKey"></typeparam>
        /// <returns></returns>
        public static Expression<Func<T, bool>> CreateContainsExpression<T, TKey>(string name, IEnumerable<TKey> values,
            string expName = "x")
        {
            var type = typeof(T);
            var parameter = Expression.Parameter(type, expName);
            var valuesParameter = Expression.Constant(values.ToList(), typeof(List<TKey>));
            var memberExpression = Expression.Property(parameter, name);
            var nameParameter = Expression.Convert(memberExpression, typeof(TKey));
            var body = Expression.Call(valuesParameter, "Contains", Array.Empty<Type>(), nameParameter);

            return Expression.Lambda<Func<T, bool>>(body, parameter);
        }


        /// <summary>
        /// 生成表达式树 例如：( x=> new [] {1,2,3}.Contains(x.Key) )
        /// </summary>
        /// <param name="name"></param>
        /// <param name="values"></param>
        /// <param name="expName"></param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static Expression<Func<T, bool>> CreateContainsExpression<T>(string name, IEnumerable<object> values,
            string expName = "x")
            => CreateContainsExpression<T, object>(name, values, expName);

        /// <summary>
        /// 生成表达式树 例如：( x=>x.Key==Guid.Empty )
        /// </summary>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <param name="expName"></param>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="TKey"></typeparam>
        /// <returns></returns>
        public static Expression<Func<T, bool>> CreateEqualExpression<T, TKey>(string name, TKey value,
            string expName = "x")
        {
            //创建 Where Lambda表达式树
            var type = typeof(T);
            var parameter = Expression.Parameter(type, expName);

            var propertyParameter = Expression.Property(parameter, name);
            var nameParameter = Expression.Convert(propertyParameter, typeof(TKey));
            var valueParameter = Expression.Constant(value, typeof(TKey));

            var body = Expression.Equal(nameParameter, valueParameter);
            return Expression.Lambda<Func<T, bool>>(body, parameter);
        }

        /// <summary>
        /// 生成表达式树 例如：( x=>x.Key.contains("123") )
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <param name="expName"></param>
        /// <returns></returns>
        public static Expression<Func<T, bool>> CreateStringContainsExpression<T>(string name, string value,
           string expName = "x")
        {
            var type = typeof(T);
            var parameter = Expression.Parameter(type, expName);

            var propertyParameter = Expression.Property(parameter, name);
            var nameParameter = Expression.Convert(propertyParameter, typeof(string));
            var valueParameter = Expression.Constant(value, typeof(string));

            var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
            var containsExp = Expression.Call(nameParameter, containsMethod, valueParameter);
            
            return Expression.Lambda<Func<T, bool>>(containsExp, parameter);
        }

        /// <summary>
        /// 生成表达式树 例如：( x=>x.Key==Guid.Empty )
        /// </summary>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <param name="expName"></param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static Expression<Func<T, bool>> CreateEqualExpression<T>(string name, object value,
            string expName = "x")
            => CreateEqualExpression<T, object>(name, value, expName);

        /// <summary>
        /// 获取 Expression {Func{T, T}} 树结构 Lambda => | x => new User{ Name="lhr" }
        /// </summary>
        /// <param name="model"></param>
        /// <param name="expName"></param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static Expression<Func<T, T>> CreateMemberInitByModel<T>(this T model, string expName = "x")
            where T : class, new()
        {
            //创建 Where Lambda表达式树
            var type = typeof(T);
            var parameter = Expression.Parameter(type, expName);

            var list = new List<MemberBinding>();

            foreach (var item in GetPropertyInfos(type))
                list.Add(Expression.Bind(item, Expression.Constant(item.GetValue(model), item.PropertyType)));

            var newExpr = Expression.New(typeof(T));

            return Expression.Lambda<Func<T, T>>(Expression.MemberInit(newExpr, list), parameter);
        }



        public static IQueryable<T> OrderByField<T>(this IQueryable<T> data, string fieldName, bool desc)
        {
            if (fieldName == null || typeof(T).GetProperty(fieldName) == null)
                return data;

            var type = typeof(T);
            var property = type.GetProperty(fieldName);
            var param = Expression.Parameter(typeof(T), "x");
            var propertyAcess = Expression.MakeMemberAccess(param, property);
            var sortExpression = Expression.Lambda(propertyAcess, param);

            var cmd = desc ? "OrderByDescending" : "OrderBy";

            var result = Expression.Call(
                typeof(Queryable),
                cmd,
                new Type[] { type, property.PropertyType },
                data.Expression,
                Expression.Quote(sortExpression));

            return data.Provider.CreateQuery<T>(result);
        }

        public static IEnumerable<T> EaWhereIf<T>(this IEnumerable<T> query, bool @if, Func<T, bool> expWhere)
            => @if ? query.Where(expWhere) : query;

        public static IQueryable<T> QaWhereIf<T>(this IQueryable<T> query, bool @if, Expression<Func<T, bool>> expWhere)
            => @if ? query.Where(expWhere) : query;

        public static IQueryable<T> Page<T>(this IQueryable<T> query, int page, int rows)
            => query.Skip((page - 1) * rows).Take(rows);
    }
}
